using System;
using System.Collections;
using System.Collections.Generic;

namespace Tarkin.InfrastructureLibrary.Utilities.Collections
{
    /// <summary>
    /// The Set class is a generic collection that emulates a mathematical set. Within Tarkin Sets are used
    /// to represent non-ordered assoication end points. Ordered association end points are implemented using
    /// the .NET Framework's generic List class. The Tarkin Set class is based on the Java class of the 
    /// same name and emulates the functioning of that class. The Tarkin Set class is a thin wrapper of the
    /// .NET Framework's generic List class.
    /// </summary>
    /// <typeparam name="T">The type of objects to be contained in the Set.</typeparam>
    /// <remarks>Tarkin classes are NOT thread-safe.</remarks>
    public class Set < T > : ICollection < T >
    {

#region Private Members

        /// <summary>
        /// Collection of Set object members.
        /// </summary>
        private List < T > setMembers = null;

#endregion


#region Private Properties

        /// <summary>
        /// Collection of Set object members.
        /// </summary>
        private List < T > SetMembers
        {
            get
            {
                if ( setMembers == null )
                {
                    setMembers = new List < T > ();
                }

                return setMembers;
            }
        }

        /// <summary>
        /// Wether the Set is read-only or not.
        /// </summary>
        public bool IsReadOnly
        {
            get
            {
                return false;
            }
        }

#endregion


#region Constructors

        /// <summary>
        /// Default cosntructor.
        /// </summary>
        public Set ()
        { }

        /// <summary>
        /// Overloaded constructor.
        /// </summary>
        /// <param name="item">An object to add to the Set upon construction.</param>
        public Set ( T item )
        {
            SetMembers.Add ( item );
        }

#endregion


#region Public Properties

        /// <summary>
        /// Returns the number of members of the set.
        /// </summary>
        public int Count 
        {
            get 
            {
                return SetMembers.Count;
            }
        }

#endregion


#region Public Methods

        /// <summary>
        /// Add an object to the Set.
        /// </summary>
        /// <param name="item">The object to be added to the Set.</param>
        /// <remarks>NOTE - This method throws exceptions in the event of a duplicate object of a value of null
        /// being passed as a parameter.</remarks>
        public void Add ( T item )
        {
            if ( item == null )
            {
                throw new ArgumentNullException ( "Attempt to add null object to the Set." );
            }

            if ( SetMembers.Contains ( item ) )
            {
                throw new ArgumentException ( string.Format ( "Attempt to add duplicate object {0} to the Set.", item.ToString () ) );
            }

            SetMembers.Add ( item );
        }

        /// <summary>
        /// Remove all items from the Set.
        /// </summary>
        public void Clear ()
        {
            SetMembers.Clear ();
        }

        /// <summary>
        /// Determines if the Set contains a specified object.
        /// </summary>
        /// <param name="item">The object to search for.</param>
        /// <returns>Whether the object is in the Set.</returns>
        public bool Contains ( T item )
        {
            return SetMembers.Contains ( item );
        }

        /// <summary>
        /// Copies the entire Set to a compatible one-dimensional array.
        /// </summary>
        /// <param name="array">The array that the Set is copied to.</param>
        public void CopyTo ( T [] array )
        {
            SetMembers.CopyTo ( array );
        }

        /// <summary>
        /// Copies the entire Set to a compatible one-dimensional array starting at the
        /// specified index of the target array.
        /// </summary>
        /// <param name="array">The array that the Set is copied to.</param>
        /// <param name="arrayIndex">The index of the array where copying begins.</param>
        public void CopyTo ( T [] array, int arrayIndex )
        {
            SetMembers.CopyTo ( array, arrayIndex );
        }

        /// <summary>
        /// Copies a specified range of the Set to the target array starting at the 
        /// specified index of the target array.
        /// </summary>
        /// <param name="index">The starting position within the Set to begin copying.</param>
        /// <param name="array">The array that the Set is copied to.</param>
        /// <param name="arrayIndex">The index of the array where copying begins.</param>
        /// <param name="count">The number of objects in the Set to copy to the array.</param>
        public void CopyTo ( int index,
                             T [] array,
                             int arrayIndex,
                             int count )
        {
            SetMembers.CopyTo ( index, array, arrayIndex, count );
        }

        /// <summary>
        /// Remove an object from the Set.
        /// </summary>
        /// <param name="item">The object to be removed from the Set.</param>
        /// <returns>True if the item was successfully removed from the Set, false if not.</returns>
        public bool Remove ( T item )
        {
            return SetMembers.Remove ( item );
        }

        /// <summary>
        /// Returns an enumerator to iterate through the Set
        /// </summary>
        /// <returns>The strongly-typed enumerator for the Set.</returns>
        public IEnumerator < T > GetEnumerator ()
        {
            return SetMembers.GetEnumerator ();
        }

        /// <summary>
        /// Returns an enumerator to iterate through the Set.
        /// </summary>
        /// <returns>The loosely-typed enumerator for the Set.</returns>
        IEnumerator IEnumerable.GetEnumerator ()
        {
            return (IEnumerator)this.GetEnumerator ();
        }

        /// <summary>
        /// Add a collection of objects to the Set.
        /// </summary>
        /// <param name="collection">The collection of objects to add to the Set.</param>
        /// <remarks>NOTE - This method leverages the Add method, therefore this method
        /// throws the same exceptions as the Add method.</remarks>
        public void AddAll ( ICollection < T > collection )
        {
            foreach ( T item in collection )
            {
                Add ( item );
            }
        }

        /// <summary>
        /// Determines if the Set contains all the objects in the passed-in collection.
        /// </summary>
        /// <param name="collection">The collection of objects to check against the Set.</param>
        /// <returns>True if all the objects in the collection are in the Set, false otherwise.</returns>
        public bool ContainsAll ( ICollection < T > collection )
        {
            foreach ( T item in collection )
            {
                if ( !SetMembers.Contains ( item ) )
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Remove all objects in the Set that are in the passed-in collection.
        /// </summary>
        /// <param name="collection">The collection of objects to remove from the Set.</param>
        public void RemoveAll ( ICollection < T > collection )
        {
            foreach ( T item in collection )
            {
                SetMembers.Remove ( item );
            }
        }

        /// <summary>
        /// Retains only those objects in the Set that are in the passed-in collection.
        /// </summary>
        /// <param name="collection">The collection of objects to retain in the Set.</param>
        public void RetainAll ( ICollection < T > collection )
        {
            int i = SetMembers.Count - 1;

            for ( ; i >= 0; i-- )
            {                        
                if ( !collection.Contains ( SetMembers [ i ] ) )
                {
                    SetMembers.RemoveAt ( i );
                }
            }
        }

#endregion

    }
}
